//
// Created by YE on 2024/2/17.
//
#include "CFunction.h"

void print(Object *object1) {
    switch (object1->type) {
        case Ot_None:
            printf("None");
            break;
        case Ot_Int:
            printf("%lld", object1->value.i);
            break;
        case Ot_Float:
            printf("%lf", object1->value.f);
            break;
        case Ot_Bool:
            object1->value.b ? printf("true") : printf("false");
            break;
        case Ot_Mat_V:
            printf("%f", *(Float *) object1->value.p);
            break;
        case Ot_Mat:
            if (object1->value.p) {
                printf("[");
                for (int i = 0; i < ((Mat *) object1->value.p)->rows; i++) {
                    printf("[");
                    for (int j = 0; j < ((Mat *) object1->value.p)->cols; j++)
                        printf("%.3f ", ((Mat *) object1->value.p)->values[i][j]);
                    (i == ((Mat *) (object1->value.p))->rows - 1) ? printf("]") : printf("]\n");
                }
                printf("]");
            }
            break;
        case Ot_String:
            printf("%s", ((String *) object1->value.p)->data);
            break;
        case Ot_List:
            printf("[");
            Object *object = object1->value.p;
            while (object->type != Ot_T) {
                print(object);
                object++;
                if (object->type != Ot_T)printf(",");
            }
            printf("]");
            break;
        default:
            printf("None");
    }
}

void C_print(Object *object1, Object *object2) {
    switch (object1->type) {
        case Ot_None:
            (void *) &object2->value ? printf("None\n") : printf("None");
            break;
        case Ot_Int:
            (void *) &object2->value ?
            printf("%lld\n", object1->value.i) : printf("%lld", object1->value.i);
            break;
        case Ot_Float:
            (void *) &object2->value ?
            printf("%lf\n", object1->value.f) : printf("%lf", object1->value.f);
            break;
        case Ot_Bool:
            if (object1->value.b) {
                (void *) &object2->value ? printf("true\n") : printf("true");
            } else {
                (void *) &object2->value ? printf("false\n") : printf("false");
            }
            break;
        case Ot_Mat_V:
            (void *) &object2->value ? printf("%f\n", *(Float *) object1->value.p) : printf("%f",
                                                                                            *(Float *) object1->value.p);
            break;
        case Ot_Mat:
            if (object1->value.p) {
                printf("[");
                for (int i = 0; i < ((Mat *) object1->value.p)->rows; i++) {
                    printf("[");
                    for (int j = 0; j < ((Mat *) object1->value.p)->cols; j++)
                        printf("%.3f ", ((Mat *) object1->value.p)->values[i][j]);
                    (i == ((Mat *) (object1->value.p))->rows - 1) ? printf("]") : printf("]\n");
                }
                (void *) &object2->value ? printf("]\n") : printf("]");
            }
            break;
        case Ot_String:
            (void *) &object2->value ?
            printf("%s\n", ((String *) object1->value.p)->data) : printf("%s", ((String *) object1->value.p)->data);
            break;
        case Ot_List:
            printf("[");
            Object *object = object1->value.p;
            while (object->type != Ot_T) {
                print(object);
                object++;
                if (object->type != Ot_T)printf(",");
            }
            (void *) &object2->value ? printf("]\n") : printf("]");
            break;
        case Ot_P:
            print(object1->value.p);
            if ((void *) &object2->value) printf("\n");
            break;
        default:
            (void *) &object2->value ? printf("None\n") : printf("None");
    }
}

Object C_Int(Object *object1) {
    Object object;
    switch (object1->type) {
        case Ot_Int:
            object = *object1;
            break;
        case Ot_Float:
            object.type = Ot_Int;
            object.value.i = object1->value.f;
            break;
        case Ot_Bool:
            object.type = Ot_Int;
            object.value.i = object1->value.b;
            break;
        case Ot_Mat_V:
            object.type = Ot_Int;
            object.value.i = *(Float *) object1->value.p;
            break;
        default:
            object.type = Ot_None;
    }
    return object;
}

Object C_Float(Object *object1) {
    Object object;
    switch (object1->type) {
        case Ot_Int:
            object.type = Ot_Float;
            object.value.f = object1->value.i;
            break;
        case Ot_Float:
            object = *object1;
            break;
        case Ot_Bool:
            object.type = Ot_Float;
            object.value.f = object1->value.b;
            break;
        case Ot_Mat_V:
            object.type = Ot_Float;
            object.value.f = *(Float *) object1->value.p;
            break;
        default:
            object.type = Ot_None;
    }
    return object;
}

void gaussianElimination(Float **values_a, Float **values_b, Float **values_x, int Am, int An, int Bn) {
    int i, j, k;
    double factor;
    for (i = 0; i < Am - 1; i++) {
        for (j = i + 1; j < Am; j++) {
            factor = values_a[j][i] / values_a[i][i];
            for (k = i; k < An; k++) {
                values_a[j][k] -= factor * values_a[i][k];
            }
            for (k = 0; k < Bn; k++) {
                values_b[j][k] -= factor * values_b[i][k];
            }
        }
    }
    for (k = 0; k < Bn; k++) {
        for (i = An - 1; i >= 0; i--) {
            values_x[i][k] = values_b[i][k];
            for (j = i + 1; j < An; j++) {
                values_x[i][k] -= values_a[i][j] * values_x[j][k];
            }
            values_x[i][k] /= values_a[i][i];
        }
    }
}

Object C_solveMat(Object *object1, Object *object2) {
    Object object;
    if (object1->type != Ot_Mat || object2->type != Ot_Mat) {
        object.type = Ot_None;
        return object;
    }
    Mat *a = copyMat(object1->value.p), *b = copyMat(object2->value.p);
    object.value.p = createMat(a->cols, b->cols);
    gaussianElimination(a->values, b->values, ((Mat *) object.value.p)->values, a->rows, a->cols, b->cols);
    deleteMat(a);
    deleteMat(b);
    object.type = Ot_Mat;
    return object;
}

Object C_createMat(Object *object1, Object *object2, Object *object3, Object *object4) {
    Object object;
    object.type = Ot_None;
    Float v;
    int m = object1->value.i, n = object2->value.i;
    if (object1->type != Ot_Int || object2->type != Ot_Int || object4->type != Ot_Int || m <= 0 || n <= 0) {
        return object;
    }
    if (object3->type == Ot_Int)
        v = object3->value.i;
    else if (object3->type == Ot_Float)
        v = object3->value.f;
    else
        return object;

    int t = object4->value.i;
    object.type = Ot_Mat;
    object.value.p = createMat(m, n);
    Float **mat = ((Mat *) object.value.p)->values;
    switch (t) {
        case 0:
            for (int i = m - 1; i >= 0; --i) {
                memset(mat[i], 0, sizeof(Float) * n);
                for (int j = n - 1; j >= 0; --j)
                    if (i == j)
                        mat[i][j] = v;
            }
            break;
        case 1:
            for (int i = m - 1; i >= 0; --i) {
                for (int j = n - 1; j >= 0; --j)
                    mat[i][j] = v;
            }
            break;
        default:
            for (int i = m - 1; i >= 0; --i) {
                memset(mat[i], 0, sizeof(Float) * n);
                for (int j = n - 1; j >= 0; --j)
                    if (i == j)
                        mat[i][j] = 1;
            }
            break;
    }
    return object;
}

Object C_transposeMat(Object *object1) {
    Object object;
    if (object1->type != Ot_Mat) {
        object.type = Ot_None;
        return object;
    }
    Mat *new_mat, *old_mat = object1->value.p;
    object.value.p = new_mat = createMat(old_mat->cols, old_mat->rows);
    for (int i = old_mat->rows - 1; i >= 0; --i) {
        for (int j = old_mat->cols - 1; j >= 0; --j) {
            new_mat->values[j][i] = old_mat->values[i][j];
        }
    }
    object.type = Ot_Mat;
    return object;
}

Object C_getMatRows(Object *object1) {
    Object object;
    if (object1->type != Ot_Mat) {
        object.type = Ot_None;
        return object;
    }
    object.type = Ot_Int;
    object.value.i = ((Mat *) object1->value.p)->rows;
    return object;
}

Object C_getMatCols(Object *object1) {
    Object object;
    if (object1->type != Ot_Mat) {
        object.type = Ot_None;
        return object;
    }
    object.type = Ot_Int;
    object.value.i = ((Mat *) object1->value.p)->cols;
    return object;
}

Object C_scanI() {
    Object object;
    object.type = Ot_Int;
    scanf("%lld", &object.value.i);

    return object;
}

Object C_scanF() {
    Object object;
    object.type = Ot_Float;
    scanf("%lf", &object.value.f);
    return object;
}

const CFuncInfo CFuncInfos[C_FUNC_SIZE] = {{"print",        2, C_print,        CFT_V2},
                                           {"Int",          1, C_Int,          CFT_O1},
                                           {"Float",        1, C_Float,        CFT_O1},
                                           {"solveMat",     2, C_solveMat,     CFT_O2},
                                           {"createMat",    4, C_createMat,    CFT_O4},
                                           {"transposeMat", 1, C_transposeMat, CFT_O1},
                                           {"rows",         1, C_getMatRows,   CFT_O1},
                                           {"cols",         1, C_getMatCols,   CFT_O1},
                                           {"scanI",        0, C_scanI,        CFT_O0},
                                           {"scanF",        0, C_scanI,        CFT_O0},
};